跳到主要内容

文件拖拽、剪贴板、系统对话框在渲染进程怎么用?

考察点​

  • 渲染进程与系统能力的交互路径
  • webUtils.getPathForFile 等新 API(Electron 32+)

参考答案​

1. 文件拖拽​

(1)HTML5 拖拽基础

dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // 必须阻止默认,否则 drop 不触发
e.dataTransfer.dropEffect = 'copy';
});

dropZone.addEventListener('drop', async (e) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);

// Electron 32+:File 对象的 path 属性已移除
// 用 webUtils.getPathForFile 获取文件系统路径
for (const file of files) {
const filePath = window.api.getPathForFile(file); // preload 桥接 webUtils
await window.api.importFile(filePath);
}
});
// preload
const { contextBridge, webUtils, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('api', {
// webUtils.getPathForFile 是 Electron 32+ 获取拖拽文件路径的官方方式
getPathForFile: (file) => webUtils.getPathForFile(file),
importFile: (path) => ipcRenderer.invoke('file:import', path),
});

历史变迁(高频考点):Electron 32 之前 File.path 可直接拿路径,之后出于安全被移除,迁移到 webUtils.getPathForFile——升级老项目必踩。

(2)从应用拖出到外部

// 渲染进程通知主进程开始拖拽
ipcRenderer.send('drag:start', filePath);

// 主进程
ipcMain.on('drag:start', (e, filePath) => {
e.sender.startDrag({
file: filePath,
icon: dragIcon, // 拖拽时显示的图标
});
});

2. 剪贴板​

(1)Web API(渲染进程内,优先)

// 写文本
await navigator.clipboard.writeText('内容');

// 读文本(需要权限/焦点)
const text = await navigator.clipboard.readText();

// 写图片等富内容(ClipboardItem)
await navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob }),
]);

(2)Electron clipboard 模块(主进程,能力更全)

需要桥接的场景:

  • 后台静默读写(页面无焦点时 Web API 受限)
  • 特殊格式:clipboard.readHTML、readRTF、readBookmark
  • macOS 的 clipboard.writeFindText(查找粘贴板)
// 主进程
const { clipboard } = require('electron');
ipcMain.handle('clipboard:read-image', () => {
const image = clipboard.readImage();
return image.isEmpty() ? null : image.toDataURL();
});

3. 系统对话框(必须走主进程)​

文件选择/保存对话框是主进程能力:

// 渲染进程
const files = await window.api.pickFiles({ filters: [{ name: '图片', extensions: ['png', 'jpg'] }] });

// preload
pickFiles: (options) => ipcRenderer.invoke('dialog:pick-files', options),

// 主进程
ipcMain.handle('dialog:pick-files', async (e, options) => {
const win = BrowserWindow.fromWebContents(e.sender);
const result = await dialog.showOpenDialog(win, {
properties: ['openFile', 'multiSelections'],
...options,
});
return result.canceled ? [] : result.filePaths;
});

要点:

  • dialog 要绑定父窗口(传 win),在 macOS 上以 sheet 形式展现,体验原生
  • 返回的是文件路径数组,文件读取在主进程做(渲染进程不该拿到任意路径读文件的能力,见安全篇)
  • macOS 沙盒化应用涉及安全作用域书签(security-scoped bookmarks)保持长期访问权

4. 其他常用系统能力速查​

能力路径
系统通知主进程 new Notification()
打开外部链接shell.openExternal(校验协议白名单)
打开文件/文件夹shell.openPath / shell.showItemInFolder
应用/系统信息app.getPath('userData') 等
电源/锁屏powerMonitor

5. 面试话术​

原则是"Web API 够用就用 Web API,不够再桥接主进程":剪贴板优先 navigator.clipboard,后台/特殊格式才走主进程 clipboard 模块;拖拽文件路径在 Electron 32 后必须 webUtils.getPathForFile(File.path 已移除);系统对话框一律主进程 showOpenDialog 并绑定父窗口。安全红线:对话框返回的路径处理在主进程完成,渲染进程不获得任意文件读能力。

常见追问​

  • 拖拽进窗口的文件能直接读内容吗? 可以(File 对象的 Web API arrayBuffer/text),但拿不到路径就走不了"路径引用"场景(如不复制直接引用原文件)。
  • 怎么实现拖拽上传并显示进度? 文件路径给主进程 → 主进程流式上传 → 进度事件推回渲染进程。